package tw.com.hitevision.whiteboard.android.widgets;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ActionMode;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputConnectionWrapper;
import android.widget.TextView;

import java.lang.reflect.Field;

import androidx.appcompat.widget.AppCompatEditText;
import androidx.core.content.ContextCompat;
import tw.com.hitevision.whiteboard.android.R;
import tw.com.hitevision.whiteboard.android.Whiteboard;
import tw.com.hitevision.whiteboard.android.display.DisplayDpiHelper;
import tw.com.hitevision.whiteboard.android.render.Word;
import tw.com.hitevision.whiteboard.android.util.TextLayoutUtil;

public class ModifiedEditTextForSkia extends AppCompatEditText {
    public static final int KEYCODE_INVALID = -1;
    private static final String TAG = ModifiedEditTextForSkia.class.getSimpleName();
    private float translateX, translateY, degree;
    private int currentKeyCode;
    private boolean isComposing;
    private String oldComposingText;
    private Whiteboard whiteboard;

    private long oldPressTime;
    private int tapCounts;
    private int selectionStart, selectionEnd;
    private boolean dragging;
    private float boundX, boundY, boundW, boundH;
    private boolean edited;
    private float minWidthAfterEdited;

    /** flag to avoid from entering infinite loop of textWatcher. */
    private boolean textWatcherDisable;
    /** flag to avoid from changing text inside textArea and triggering undo method. */
    private boolean textToTextAreaDisable;

    private boolean isUsingInSearchImage;

    private int paddingStart;
    private int paddingEnd;
    private int paddingTop;
    private int paddingBottom;

    public ModifiedEditTextForSkia(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public ModifiedEditTextForSkia(Context context) {
        super(context);
        init(context);
    }

    private void init(Context context){
        float factor = DisplayDpiHelper.getInstance().getFake4kFactor();
        // after changDPI so factor value use 1
        factor = 1;

        translateX = 0.0f;
        translateY = 0.0f;
        degree = 0.0f;
        currentKeyCode = KEYCODE_INVALID;
        textWatcherDisable = false;
        textToTextAreaDisable = false;
        isComposing = false;
        oldComposingText = "";
        // Do not use Paint.LINEAR_TEXT_FLAG, it causes the text width synchronization bug.
//        this.getPaint().setFlags(Paint.ANTI_ALIAS_FLAG|Paint.FILTER_BITMAP_FLAG|Paint.LINEAR_TEXT_FLAG);
//        this.getPaint().setStyle(Paint.Style.FILL);
        setPaintFlags(Paint.ANTI_ALIAS_FLAG|Paint.FILTER_BITMAP_FLAG);
        getPaint().setAntiAlias(true);

        tapCounts = 0;
        dragging = false;
        edited = false;

        isUsingInSearchImage = false;

        setHighlightColor(ContextCompat.getColor(context, R.color.text_highlight_color));
//        setMinWidth(50);
//        setInputType(Word.DEFAULT_INPUT_TYPE);
//        setPadding(Word.PADDING, Word.PADDING, Word.PADDING, Word.PADDING);
//        setGravity(Gravity.TOP| Gravity.LEFT);

        paddingStart = (int) (context.getResources().getDimensionPixelSize(R.dimen.word_text_box_padding_start) * factor);
        paddingEnd = (int) (context.getResources().getDimensionPixelSize(R.dimen.word_text_box_padding_end) * factor);
        paddingTop = (int) (context.getResources().getDimensionPixelSize(R.dimen.word_text_box_padding_top) * factor);
        paddingBottom = (int) (context.getResources().getDimensionPixelSize(R.dimen.word_text_box_padding_bottom) * factor);
        setPadding(paddingStart, paddingTop, paddingEnd, paddingBottom);
        setMaxHeight(Integer.MAX_VALUE);

        // hide copy, paste, text selection toolbar
        // https://stackoverflow.com/questions/41673185/disable-edittext-context-menu
//        this.setCustomSelectionActionModeCallback(new ActionMode.Callback() {
//            @Override
//            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
//                return false;
//            }
//
//            @Override
//            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
//                return false;
//            }
//
//            @Override
//            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
//                return false;
//            }
//
//            @Override
//            public void onDestroyActionMode(ActionMode mode) {
//
//            }
//        });

//        setLongClickable(false);
//        setTextIsSelectable(false);

        this.setOnTouchListener(new OnTouchListener() {
//            @Override
//            public boolean onTouch(View v, MotionEvent event) {
//                clearFocus();
//                return false;
//            }
            @Override
            public boolean onTouch(View v, MotionEvent event) {

                switch(event.getAction()){
                    case MotionEvent.ACTION_DOWN:
                    case MotionEvent.ACTION_POINTER_DOWN:{
                        dragging = false;
                        int cur = TextLayoutUtil.getCursorByEvent(ModifiedEditTextForSkia.this, event, false);
                        if (cur != getSelectionStart() || cur != getSelectionEnd()) {
                            setSelection(cur);
                        }

                        long currentTime = System.currentTimeMillis();
                        if (currentTime-oldPressTime<=Word.TAP_PERIOD) {
                            tapCounts++;
                            if (tapCounts>=3) {
                                setSelection(0, getText().length());
                            }
                        } else {
                            tapCounts = 1;
                        }
                        oldPressTime = currentTime;

                        selectionStart = getSelectionStart();
                        selectionEnd = getSelectionEnd();
                        break;
                    }
                    case MotionEvent.ACTION_MOVE:{
                        if (tapCounts>=3 || isUsingInSearchImage) {
                            break;
                        }

                        int cur = TextLayoutUtil.getCursorByEvent(ModifiedEditTextForSkia.this, event, false);

                        if (cur != selectionStart || cur != selectionEnd) {
                            selectionEnd = cur;
                            setSelection(Math.min(selectionStart, cur), Math.max(selectionStart, cur));
                            dragging = true;
                        }

                        break;
                    }
                    case MotionEvent.ACTION_UP:
                    case MotionEvent.ACTION_POINTER_UP:{
                        selectionStart = getSelectionStart();
                        selectionEnd = getSelectionEnd();
                        if (dragging || tapCounts>=3) {
                            dragging = false;
                            return true;
                        }
                        break;
                    }
                }

                // TODO : need to test it is necessary
                if (!isUsingInSearchImage) {
                    clearFocus();
                }
                return false;
            }
        });
    }

    public void setUsingInSearchImage(boolean b) {
        isUsingInSearchImage = b;
        setPadding(getPaddingLeft(), getPaddingTop(), 72, getPaddingBottom());
    }

    @Override
    public void setSelection(int start, int stop) {
        super.setSelection(start, stop);
    }

    public void setWhiteboardLis(Whiteboard w){
        whiteboard = w;
    }

    public void setCursor(int cur) {
        dragging = false;
        setSelection(cur);
        oldPressTime = System.currentTimeMillis();
        selectionStart = getSelectionStart();
        selectionEnd = selectionStart;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            // setInsertionDisabled when user touches the view
            this.setInsertionDisabled();
        }
        return super.onTouchEvent(event);
    }

    // disable the tear like cursor
    private void setInsertionDisabled() {
        try {
            Field editorField = TextView.class.getDeclaredField("mEditor");
            editorField.setAccessible(true);
            Object editorObject = editorField.get(this);

            Class editorClass = Class.forName("android.widget.Editor");
            Field mInsertionControllerEnabledField = editorClass.getDeclaredField("mInsertionControllerEnabled");
            mInsertionControllerEnabledField.setAccessible(true);
            mInsertionControllerEnabledField.set(editorObject, false);
        }
        catch (Exception ignored) {
            // ignore exception here
        }
    }

    @Override
    public boolean isSuggestionsEnabled() {
        return false;
    }

    /**
     * An EditText, which notifies when something was cut/copied/pasted inside it.
     * @author Lukas Knuth
     *  https://stackoverflow.com/questions/14980227/android-intercept-paste-copy-cut-on-edittext/14981376
     */
    @Override
    public boolean onTextContextMenuItem(int id) {
        // Do your thing:
        boolean consumed = super.onTextContextMenuItem(id);
        // React:
        switch (id){
            case android.R.id.cut:
                onTextCut();
                break;
            case android.R.id.paste:
                onTextPaste();
                break;
            case android.R.id.copy:
                onTextCopy();
        }
        return false;
//        return super.onTextContextMenuItem(id);
    }

    private void onTextCut(){
    }

    private void onTextPaste(){
    }

    private void onTextCopy(){
    }

    public void modifiedSetTranslate(float trX, float trY){
        translateX = trX;
        translateY = trY;
    }

    @Override
    public void draw(Canvas canvas){

        canvas.save();

        if (translateX!=0.0f || translateY!=0.0f) {
            canvas.translate(translateX, translateY);
        }
        super.draw(canvas);

        canvas.restore();
    }

    @Override
    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
        return new ModifiedInputConnection(super.onCreateInputConnection(outAttrs), true);
    }

    private class ModifiedInputConnection extends InputConnectionWrapper {

        public ModifiedInputConnection(InputConnection target, boolean mutable) {
            super(target, mutable);
        }

        @Override
        public boolean setComposingText(CharSequence text, int newCursorPosition) {
            if (isComposing && oldComposingText!=null && oldComposingText.length()-text.length()==1) {
                currentKeyCode = KeyEvent.KEYCODE_DEL;
            }

            int cursorPosition = getSelectionEnd();
            oldComposingText = text.toString();
            isComposing = true;

            return super.setComposingText(text, newCursorPosition);
        }

        @Override
        public boolean commitText(CharSequence text, int newCursorPosition) {
            isComposing = false;
            return super.commitText(text, newCursorPosition);
        }

        @Override
        public boolean finishComposingText() {
            isComposing = false;
            return super.finishComposingText();
        }

        @Override
        public boolean deleteSurroundingText(int beforeLength, int afterLength) {
            boolean backspaceByIME = (beforeLength>0 && afterLength==0); // delete text on the left side
            boolean deleteByIME = (beforeLength==0 && afterLength>0);

            String oldText = getText().toString();
            int preSelStart = getSelectionStart();
            int preSelEnd = getSelectionEnd();
            int preSelectionLength = preSelEnd - preSelStart;
            currentKeyCode = (backspaceByIME ? (preSelStart!=preSelEnd ? KEYCODE_INVALID : KeyEvent.KEYCODE_DEL) : KEYCODE_INVALID);

//            log("ModifiedEditText deleteSurroundingText : before["+oldText+"] selection("+preSelStart+", "+preSelEnd+")"
//                    +"length("+beforeLength+", "+afterLength+") backspaceByIME :"+backspaceByIME+" KeyCode:"+currentKeyCode);

            textWatcherDisable = true;

            boolean result = super.deleteSurroundingText(beforeLength, afterLength);
            if (!result) {
                textWatcherDisable = false;
                return false;
            }

            String newText = getText().toString();
            int newSelStart = getSelectionStart();
            int newSelEnd = getSelectionEnd();
            int actualDeleteLength = oldText.length() - newText.length();

            getText().clear();
            getText().append(oldText);
            setSelection(preSelStart, preSelEnd);

            textWatcherDisable = false;

            boolean revert = true;

            if (backspaceByIME && preSelStart!=preSelEnd) {
                if (actualDeleteLength!=preSelectionLength || newSelStart!=newSelEnd || newSelStart!=preSelStart ||
                        !oldText.substring(0, preSelStart).equals(newText.substring(0,newSelStart)) ||
                        !oldText.substring(preSelEnd, oldText.length()).equals(newText.substring(newSelStart, newText.length())) ) {
//                    log("ModifiedEditText deleteSurroundingText : special case :selection("+preSelStart+", "+preSelEnd+
//                            ") length("+beforeLength+", "+afterLength+")");
                    setSelection(preSelStart, preSelStart);
                    getText().delete(preSelStart, preSelEnd);
                    revert = false;
                }
            }

            if (revert) {
                if (backspaceByIME && currentKeyCode==KeyEvent.KEYCODE_DEL) {
                    /** When press the predictive word on the suggestion bar, AI.Type keyboard will delete the original characters first
                                         * then insert the predictive word. (ex: "Q"->"Qu"->""->"Quick")
                                         * I don't want to let this event to trigger modifiedEditTextChanged method*/

                    textToTextAreaDisable = true;
                } else if (deleteByIME) {
                    // The case of delete must be triggered by IME.
                    textToTextAreaDisable = true;
                }

                result = super.deleteSurroundingText(beforeLength, afterLength);
            }

            textToTextAreaDisable = false;
            textWatcherDisable = false;

            return result;
        }

        @Override
        public boolean sendKeyEvent(KeyEvent event) {
            if (event.getAction() == KeyEvent.ACTION_DOWN){
                currentKeyCode = KEYCODE_INVALID;
                if (event.getKeyCode() == KeyEvent.KEYCODE_DEL || event.getKeyCode() == KeyEvent.KEYCODE_ENTER) {
                    currentKeyCode = event.getKeyCode();
                }
            }
            return super.sendKeyEvent(event);
        }
    }

    public void resetIsComposing(){
        isComposing = false;
        oldComposingText = "";
    }

    @Override
    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
        /**
                 * When press back button, onBackPressed() in GdxActivity will be triggered.
                 * When Keyboard shows up, onBackPressed() in GdxActivity is intercepted by IME first.
                 * Use the following method to trigger anything before IME.
                 * */
        if (keyCode==KeyEvent.KEYCODE_BACK && whiteboard!= null) {
            whiteboard.onBackButtonPressed();
            return true;
        }
        return super.onKeyPreIme(keyCode, event);
    }

    // To get more accurate center position after rotate
    public void setBound(float x, float y, float w, float h) {
        boundX = x;
        boundY = y;
        boundW = w;
        boundH = h;
    }

    public float getBoundX() {
        return boundX;
    }

    public float getBoundY() {
        return boundY;
    }

    public float getBoundW() {
        return boundW;
    }

    public float getBoundH() {
        return boundH;
    }

    public void setEdited(boolean b) {
        edited = b;
    }

    public boolean getEdited() {
        return edited;
    }

    public void setCurrentKeyCode(int a){
        currentKeyCode = a;
    }

    public int getCurrentKeyCode(){
        return currentKeyCode;
    }

    public boolean getTextWatcherDisable(){
        return textWatcherDisable;
    }

    public void setTextWatcherDisable(boolean b){
        textWatcherDisable = b;
    }

    public void setTextToTextAreaDisable(boolean b) {
        textToTextAreaDisable = b;
    }

    public boolean getTextToTextAreaDisable(){
        return textToTextAreaDisable;
    }

    public void setMinWidthAfterEdited(float w){
        minWidthAfterEdited = w;
    }

    public float getMinWidthAfterEdited(){
        return minWidthAfterEdited;
    }

    private void log(String str){
        Log.d(TAG, str);
    }
}
